package org.batatasframework.support.searchable.internal;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.batatasframework.support.database.IdentifyTable;
import org.batatasframework.support.database.IdentifyTableBase;
import org.batatasframework.support.database.IdentifyTableGenerator;
import org.batatasframework.support.database.NeedTemporaryTable;
import org.batatasframework.support.database.SelectedFieldCollection;
import org.batatasframework.support.database.jdbc.ConnectionAccessor;
import org.batatasframework.support.database.metadatas.DatabaseMetadataResource;
import org.batatasframework.support.database.metadatas.FieldMetadata;
import org.batatasframework.support.database.metadatas.TableMetadata;
import org.batatasframework.support.database.spi.ResultBuffer;
import org.batatasframework.support.database.sql.QueryParameter;
import org.batatasframework.support.database.sql.TableCriterionProxy;
import org.batatasframework.support.database.sql.TableInstance;
import org.batatasframework.support.database.sql.TableRuntimeMaster;
import org.batatasframework.support.database.sql.TableRuntimeTemporary;
import org.batatasframework.support.database.sql.field.DefaultSelectFragment;
import org.batatasframework.support.database.sql.where.ArrayQueryFragment;
import org.batatasframework.support.database.sql.where.QueryFragment;
import org.batatasframework.support.database.sql.where.QueryFragmentBuilder;
import org.batatasframework.support.database.sql.where.SingleQueryFragment;
import org.batatasframework.support.database.sql.where.ZeroQueryFragmentDefault;
import org.batatasframework.support.definition.FieldDefinitionSourcable;
import org.batatasframework.support.definition.QueryDefinition;
import org.batatasframework.support.event.HttpRequestAccessor;
import org.batatasframework.support.event.SearchableRequest;
import org.batatasframework.support.searchable.SearchableHandler;
import org.batatasframework.support.searchable.SearchableInputStream;

import cn.bonoon.kernel.util.Opt;

public abstract class AbstractSearchableHandler<T extends TableMetadata> extends TableCriterionProxy<TableRuntimeMaster> implements SearchableHandler{
	
	protected final Log logger = LogFactory.getLog(getClass());
	
	protected final DatabaseMetadataResource databaseMetadataSources;
	protected final AutoCloseable needClose;
	protected final T sourceTable;
	
	/*
	 * 如果是使用已经创建的临时表的，则这里就不应该再使用处理指标字段的方式了
	 */
	private TableRuntimeTemporary temporaryTable;

	protected IdentifyTableGenerator targetTableGenerator;
	
	public AbstractSearchableHandler(
			DatabaseMetadataResource databaseMetadataSources, 
			SearchableRequest searchableConfigurable, 
			T tableMetadata, 
			AutoCloseable needClose) throws Exception{
		super(new TableRuntimeMaster(
				databaseMetadataSources, 
				searchableConfigurable.getConnectionAccessor(), 
				tableMetadata, 
				searchableConfigurable.getAliasGenerator()));
		
		this.databaseMetadataSources = databaseMetadataSources;
		this.needClose = needClose;
		this.sourceTable = tableMetadata;
		
		this.setTargetTableGenerator(searchableConfigurable.getTableObjectGenerator());
	}

	private void createTemporaryTable() throws Exception{
		if(null == temporaryTable){
			this.temporaryTable = new TableRuntimeTemporary(sourceTableCriterion);
		}
	}

	@Override
	public void newQuery(QueryDefinition queryDefine, HttpRequestAccessor paramAccessor, String key) throws Exception {
		if(queryDefine instanceof FieldDefinitionSourcable){
			FieldDefinitionSourcable fdr = (FieldDefinitionSourcable)queryDefine;
			FieldMetadata fieldMetadata = fdr.getSourceField();
			if(fieldMetadata instanceof NeedTemporaryTable){
				createTemporaryTable();
				NeedTemporaryTable fieldElementBuilder = (NeedTemporaryTable)fieldMetadata;
				
				//相当于处理join b ... where b.field=1类似的情况
				TableInstance tableSource = sourceTableCriterion.safeLoadSource(sourceTableCriterion, fdr.getSourceTable(), true);
				sourceTableCriterion.tryAddFieldElement(tableSource, fieldMetadata, fieldElementBuilder);
				
				queryDefine.buildQuery(temporaryTable, temporaryTable, paramAccessor, key); 
				
				return;
			}
		}
		super.newQuery(queryDefine, paramAccessor, key);
	}
	
	public void setTargetTableGenerator(IdentifyTableGenerator targetTableGenerator) {
		if(null != targetTableGenerator){
			this.targetTableGenerator = targetTableGenerator;
		}
	}
	
	protected IdentifyTableGenerator createDefaultTargetTableGenerator() throws Exception{
		return IdentifyTableGenerator.DEFAULT;
	}
	
	protected void initExecutable(ResultBuffer result, SelectedFieldCollection fieldCollection) throws Exception{
		if(null != temporaryTable){
			temporaryTable.finish();
		}
		result.buildHead(fieldCollection);
	}
	
	@Override
	public SearchableInputStream execute(SearchableRequest searchableConfigurable) throws Exception {
		long startExecute = System.currentTimeMillis();
		
		IdentifyTableGenerator targetTableGenerator = this.targetTableGenerator;
		if(null == targetTableGenerator){
			targetTableGenerator = createDefaultTargetTableGenerator();
			
			if(logger.isInfoEnabled()){
				logger.info("没有定义" + IdentifyTableGenerator.class.getName() 
						+ "目标表的生成对象，使用默认的目标表生成器！");
			}
		}
		
		TableRuntimeMaster masterTableRuntime = getMasterTableRuntime();
		searchableConfigurable.resolve(masterTableRuntime, this);
		SearchableInputStream result = searchableConfigurable.getInputStream();
		initExecutable(result, masterTableRuntime);
		
		IdentifyTable[] tts = targetTableGenerator.generate(masterTableRuntime, 
				searchableConfigurable.getConnectionAccessor(), 
				searchableConfigurable.getOperator());
		
		if(null == tts || tts.length == 0) throw new Exception("当前账号无法进行查询！");
		
		List<QueryParameter> sourceParamenters = new ArrayList<>();
		String sourceWhereClause = masterTableRuntime.getWhereClause(sourceParamenters);
		String sortClause = masterTableRuntime.getSortClause();
		
		for(int i = 0; i < tts.length; i++){
			
			long startAccess = System.currentTimeMillis();
			/*
			 * 这里是完成一次数据库的访问及数据的读取操作；
			 * 如果访问的是另一台数据库服务器，则在这里可能需要关闭一些资源之类的操作，
			 * 所以这里需要使用资源的自动关闭接口来处理
			 */
			try(IdentifyTable tt = tts[i]){
				
				List<QueryParameter> queryParamenters = new ArrayList<>();
				//where和order by因为没有表查询和子查询的问题，所以如果有多个union表的，也都是相同的条件，所以不需要每次都生成
				
				/*
				 * 这里可能需要把几个分表连接起来返回结果，分表有可能是同个数据库服务器，也有可能非同个数据库服务器
				 * 
				 * 应该是在这个时候才确定需要读取哪些表(分表)，是否需要读取多个数据库的表和怎么读取和进行连接处理
				 */
				String sql = createSql(tt, queryParamenters, sourceWhereClause, sourceParamenters, sortClause);
				
				executeQuery(tt, result, sql, queryParamenters);
			}
			
			if(logger.isInfoEnabled()){
				logger.info("第[" + i + "]次访问数据库，本次查询时间：" 
						+ (System.currentTimeMillis() - startAccess));
			}
		}
		
		result.flush();
		
		if(logger.isInfoEnabled()){
			logger.info("查询数据库时间：" + (System.currentTimeMillis() - startExecute));
		}
		
		return result;
	}
	
	protected void executeQuery(IdentifyTable tt, ResultBuffer result, String sql, List<QueryParameter> queryParamenters) throws Exception{

		ConnectionAccessor connectionAccessor = tt.getConnectionAccessor();
		
		/*
		 * 如果是使用临时表的，在这个循环里，就是读取一个分表的数据，所以每一个循环应该都是一个临时表
		 * 
		 * 注意：如果是使用临时表的，应该在使用前创建，使用后应该要安全删除，因为这个连接可能会被回收重新利用的
		 */
		if(null != temporaryTable){

			//TEMPORARY TABLE  TODO 这里修改
			try{
				String sqlCreateTemporaryTable = "create temporary table " + temporaryTable.getTableName() + " " + sql;
				String ttsqlSelect = temporaryTable.getSqlSelect();
				List<QueryParameter> ttParamenters = temporaryTable.getSqlParamenters();
				
				if(logger.isInfoEnabled()){
					logger.info("需要使用临时表处理数据，查询语句：" + ttsqlSelect);
				}
				
				connectionAccessor.execute(sqlCreateTemporaryTable, queryParamenters);
			
				connectionAccessor.executeQuery(ttsqlSelect, ttParamenters, result);
				
			}finally{
				connectionAccessor.execute(temporaryTable.getSqlDrop());
			}
		}else{
			connectionAccessor.executeQuery(sql, queryParamenters, result);
		}
	}
	
	/** 解析成一条完成的查询语句 */
	protected void parseSqlStatement(
			StringBuilder sql, 
			IdentifyTableBase targetTable, 
			List<QueryParameter> queryParamenters,
			String whereClause, 
			List<QueryParameter> sourceParamenters) throws Exception{
		
		sql.append("select ").append(targetTable.getSelectClause())
			.append(" from ").append(targetTable.getFromClause());
		
		String whereTarget = targetTable.getWhereClause(queryParamenters);
		
		if(!whereClause.isEmpty() || !whereTarget.isEmpty()){
			sql.append(" where ").append(whereTarget);
			if(!whereClause.isEmpty() && !whereTarget.isEmpty()){ sql.append(" and "); }
			sql.append(whereClause);
			queryParamenters.addAll(sourceParamenters);
		}
	}
	
	protected String createSql(IdentifyTable tt,
			List<QueryParameter> queryParamenters,
			String whereClause, 
			List<QueryParameter> sourceParamenters, 
			String sortClause) throws Exception{

		StringBuilder sql = new StringBuilder();
		IdentifyTableBase ttb = tt.getUnion();
		if(null != ttb){
			sql.append("(");
			parseSqlStatement(sql, tt, queryParamenters, whereClause, sourceParamenters);
			
			do{
				sql.append(")union(");
				parseSqlStatement(sql, ttb, queryParamenters, whereClause, sourceParamenters);
				
				ttb = ttb.getUnion();
			}while(null != ttb);
			sql.append(")");
		}else{
			parseSqlStatement(sql, tt, queryParamenters, whereClause, sourceParamenters);
		}

		if(!sortClause.isEmpty()){
			sql.append(" order by ").append(sortClause);
		}
		
		return sql.toString();
	}
	
	@Override
	public TableRuntimeMaster getMasterTableRuntime(){
		return sourceTableCriterion;
	}

	
	@Override
	public void close() throws Exception {
		if(null != needClose){
			needClose.close();
		}
	}

	@Override
	public DefaultSelectFragment[] field(String... names) throws Exception {
		return sourceTableCriterion.field(sourceTableCriterion, names);
	}

	@Override
	public QueryFragmentBuilder newQueryBuilder(TableInstance tableAlias, String fieldName) throws Exception {
		FieldMetadata fieldMetadata = sourceTable.get(fieldName);
		QueryFragmentBuilder qfb =  new QueryFragmentBuilderImpl(tableAlias, fieldMetadata);
		if(fieldMetadata instanceof NeedTemporaryTable){
			createTemporaryTable();
			
			NeedTemporaryTable fieldElementBuilder = (NeedTemporaryTable)fieldMetadata;
			sourceTableCriterion.tryAddFieldElement(sourceTableCriterion, fieldMetadata, fieldElementBuilder);
		}
		return qfb;
	}

	private class QueryFragmentBuilderImpl implements QueryFragmentBuilder{
		
		private final TableInstance tableAlias;
		private final FieldMetadata fieldMetadata;
		
		public QueryFragmentBuilderImpl(TableInstance tableAlias, FieldMetadata fieldMetadata){
			this.tableAlias = tableAlias;
			this.fieldMetadata = fieldMetadata;
		}
		
		@Override
		public FieldMetadata getFieldMetadata() {
			return fieldMetadata;
		}

		@Override
		public QueryFragment build(Opt opt) throws Exception {
			return new ZeroQueryFragmentDefault(tableAlias, fieldMetadata, opt);
		}

		@Override
		public QueryFragment build(Object value) throws Exception {
			return build(Opt.EQ, value);
		}

		@Override
		public QueryFragment build(Opt opt, Object value) throws Exception {
			return new SingleQueryFragment(tableAlias, fieldMetadata, opt, value);
		}

		@Override
		public QueryFragment in(Object... values) throws Exception {
			return new ArrayQueryFragment(tableAlias, fieldMetadata, Opt.IN, values);
		}

		@Override
		public QueryFragment not(Object... values) throws Exception {
			return new ArrayQueryFragment(tableAlias, fieldMetadata, Opt.NI, values);
		}
	}
}
